﻿using System;
using System.Linq;
using K9Nano.LayIM.Hubs;
using K9Nano.LayIM.Internal;
using K9Nano.LayIM.Stores;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace K9Nano.LayIM
{
    public static class StartupExtensions
    {
        public static ILayIMAppBuilder AddLayIM(this IServiceCollection services, Action<LayIMOptions> configure)
        {
            var mvcBuilder = services.GetMvcBuilder();

            var assembly = typeof(StartupExtensions).Assembly;

            mvcBuilder.AddApplicationPart(assembly);

            services.AddMediatR(assembly);

            services.Configure(configure);

            services.AddSingleton<IChatHubContext, ChatHubContext>()
                .AddSingleton<IUserIdProvider, ClaimBasedUserIdProvider>()
                .AddSingleton<IJsonSerializer, TextJsonSerializer>()
                .AddScoped<IChatManager, ChatManager>()
                .AddSingleton<IChatGroupProvider, NullChatGroupProvider>()
                ;

            var signalRBuilder = services.AddSignalR();

            var builder = new LayIMAppBuilder(services, signalRBuilder);

            return builder;
        }

        public static ILayIMAppBuilder AddDefaultFilePolicy(this ILayIMAppBuilder app)
        {
            app.Services.Configure<AuthorizationOptions>(opt =>
            {
                opt.AddPolicy(Constants.File_Upload_Authorize_Policy, builder =>
                {
                    builder.RequireAuthenticatedUser();
                });

                opt.AddPolicy(Constants.File_Access_Authorize_Policy, builder =>
                {
                    builder.RequireAuthenticatedUser();
                });
            });

            return app;
        }

        public static ILayIMAppBuilder AddMemoryChatAppOnlineStaffCache(this ILayIMAppBuilder app)
        {
            app.Services.AddSingleton<IChatAppOnlineStaffCache, MemoryChatAppOnlineStaffCache>();
            return app;
        }

        public static ILayIMAppBuilder AddGroupProvider<T>(this ILayIMAppBuilder app) where T: IChatGroupProvider
        {
            app.Services.Replace(ServiceDescriptor.Scoped(typeof(IChatGroupProvider), typeof(T)));

            return app;
        }

        public static ILayIMAppBuilder AddRoomStore<T>(this ILayIMAppBuilder app) where T : IChatRoomStore
        {
            app.Services.AddScoped(typeof(IChatRoomStore), typeof(T));

            return app;
        }

        public static ILayIMAppBuilder AddFileStore<T>(this ILayIMAppBuilder app) where T : IFileStore
        {
            app.Services.AddScoped(typeof(IFileStore), typeof(T));

            return app;
        }

        public static ILayIMAppBuilder AddMessageStore<T>(this ILayIMAppBuilder app) where T : IChatMessageStore
        {
            app.Services.AddScoped(typeof(IChatMessageStore), typeof(T));

            return app;
        }

        public static ILayIMAppBuilder AddFriendStore<T>(this ILayIMAppBuilder app) where T : IChatFriendStore
        {
            app.Services.AddScoped(typeof(IChatFriendStore), typeof(T));

            return app;
        }

        public static IApplicationBuilder UseLayIM(this IApplicationBuilder app)
        {
            var options = app.ApplicationServices.GetService<IOptions<LayIMOptions>>().Value;

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHub<ChatHub>(options.ChatHubPath, hub =>
                {
                    hub.Transports = HttpTransportType.WebSockets | HttpTransportType.LongPolling;
                });
            });

            return app;
        }

        private static T GetServiceFromCollection<T>(this IServiceCollection services)
        {
            return (T)services
                .LastOrDefault(d => d.ServiceType == typeof(T))
                ?.ImplementationInstance;
        }

        private static IMvcBuilder GetMvcBuilder(this IServiceCollection services)
        {
            var partManager = services.GetServiceFromCollection<ApplicationPartManager>();
            if (partManager == null)
            {
                throw new Exception("Please call services.AddMvc() at first");
            }
            var builder = new MvcBuilder(services, partManager);
            return builder;
        }
    }
}